use std::collections::*;
use std::hash::Hash;

/// Generic registry type used by different subsystems.  Expected to be wrapped if additional bookkeeping is necessary.
pub struct GenericRegistry<K: RegistryKind> {
    next_id: K::RawIdent,
    names: bimap::BiHashMap<String, K::Ident>,
    regs: HashMap<K::Ident, K::Reg>,
}

impl<K: RegistryKind> GenericRegistry<K> {
    pub fn new() -> Self {
        let zero_id = <K::RawIdent as Default>::default();
        Self {
            next_id: zero_id,
            names: bimap::BiHashMap::new(),
            regs: HashMap::new(),
        }
    }

    fn consume_id(&mut self) -> K::Ident {
        let id = self.next_id;
        self.next_id = K::next_ident(id);
        id.into()
    }

    /// Adds a new registration with a new ID.
    pub fn register(&mut self, name: String, reg: K::Reg) -> bool {
        // Check if we can add it before we consume the ID.
        if self.get_by_name(&name).is_some() {
            return false;
        }

        let id = self.consume_id();
        self.names.insert(name, id);
        self.regs.insert(id, reg);
        true
    }

    /// Resolves a name to an ID.
    pub fn resolve_name(&self, name: &str) -> Option<K::Ident> {
        self.names.get_by_left(name).copied()
    }

    /// Looks up a registration by name.
    pub fn get_by_name(&self, name: &str) -> Option<&K::Reg> {
        let id = self.names.get_by_left(name)?;
        Some(self.regs.get(id).expect("registry: missing ref"))
    }

    /// Looks up a registration by its ID.
    pub fn get_by_id(&self, id: K::Ident) -> Option<&K::Reg> {
        self.regs.get(&id)
    }
}

pub trait RegistryKind {
    type RawIdent: Copy + Clone + Default;
    type Ident: Copy + Clone + Hash + Eq + PartialEq + Ord + PartialOrd + From<Self::RawIdent>;
    type Reg;

    fn next_ident(a: Self::RawIdent) -> Self::RawIdent;
}

// TODO make a `registry_project!` macro that generates a trait impl for some generic `Registry` trait
